from python cimport \
    PyString_AsStringAndSize, \
    PyString_AsString, \
    PyString_FromString, \
    PyString_FromStringAndSize

cimport tcrdb
cimport tcutil
from tcutil cimport bool, uint32_t, uint64_t, TCMAP, TCLIST

RDBXOLCKREC = tcrdb.RDBXOLCKREC
RDBXOLCKGLB = tcrdb.RDBXOLCKGLB

ESUCCESS = tcrdb.TTESUCCESS
EINVALID = tcrdb.TTEINVALID
ENOHOST  = tcrdb.TTENOHOST
EREFUSED = tcrdb.TTEREFUSED
ESEND    = tcrdb.TTESEND
ERECV    = tcrdb.TTERECV
EKEEP    = tcrdb.TTEKEEP
ENOREC   = tcrdb.TTENOREC
EMISC    = tcrdb.TTEMISC

def _complain_ifclosed(closed):
    if closed:
        raise ValueError("I/O operation on closed database")

def errmsg(ecode):
    if ecode == ESUCCESS:
        return "success"
    elif ecode == EINVALID:
        return "invalid operation"
    elif ecode == ENOHOST:
        return "connection refused"
    elif ecode == ESEND:
        return "send error"
    elif ecode == ERECV:
        return "recv error"
    elif ecode == EKEEP:
        return "existing record"
    elif ecode == ENOREC:
        return "no record found"
    elif ecode == EMISC:
        return "miscellanous error"
    else:
        return "unknown"

cdef class TT:
    cdef tcrdb.TCRDB *tcrdb
    cdef bool closed

    def __cinit__(self, *args, **kwds):
        self.tcrdb = tcrdb.tcrdbnew()
        if self.tcrdb == NULL:
            raise MemoryError()
        self.closed = 0

    def __init__(self, host, unsigned int port):
        cdef char *c_host
        cdef Py_ssize_t host_len
        cdef bool ret
        cdef int code

        PyString_AsStringAndSize(host, &c_host, &host_len)

        ret = tcrdb.tcrdbopen(self.tcrdb, c_host, port)
        if ret != 1:
            code = tcrdb.tcrdbecode(self.tcrdb)
            raise IOError(errmsg(code))

    def __dealloc__(self):
        if not self.closed:
            tcrdb.tcrdbclose(self.tcrdb)
        tcrdb.tcrdbdel(self.tcrdb)

    def ecode(self):
        cdef int code
        code = tcrdb.tcrdbecode(self.tcrdb)
        return code

    def close(self):
        """
        close the connection to TT
        """
        cdef bool ret

        _complain_ifclosed(self.closed)

        self.closed = 1
        ret = tcrdb.tcrdbclose(self.tcrdb)
        return (ret == 1)

    def put(self, key, value):
        """
        Store a record into a remote database object.
        If successful, the return value is true, else, it is false.
        If a record with the same key exists in the database, it is overwritten.
        """
        cdef Py_ssize_t key_len, value_len
        cdef char *c_key, *c_value
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)

        ret = tcrdb.tcrdbput(self.tcrdb, c_key, key_len, c_value, value_len)

        return (ret == 1)

    def putkeep(self, key, value):
        """
        Store a new record into a remote database object.
        If successful, the return value is true, else, it is false.
        If a record with the same key exists in the database, this function has no effect.
        """
        cdef Py_ssize_t key_len, value_len
        cdef char *c_key, *c_value
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)
        ret = tcrdb.tcrdbputkeep(self.tcrdb, c_key, key_len, c_value, value_len)

        return (ret == 1)

    def putcat(self, key, value):
        """
        Concatenate a value at the end of the existing record in a remote database object.
        If successful, the return value is true, else, it is false.
        If there is no corresponding record, a new record is created.
        """
        cdef Py_ssize_t key_len, value_len
        cdef char *c_key, *c_value
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)
        ret = tcrdb.tcrdbputcat(self.tcrdb, c_key, key_len, c_value, value_len)

        return (ret == 1)

    def putshl(self, key, value, int width):
        """
        Concatenate a string value at the end of the existing record and rotate it to the left.
        `width' specifies the width of the record.
        If successful, the return value is true, else, it is false.
        If there is no corresponding record, a new record is created.
        """
        cdef Py_ssize_t key_len, value_len
        cdef char *c_key, *c_value
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)
        ret = tcrdb.tcrdbputshl(self.tcrdb, c_key, key_len, c_value, value_len, width)

        return (ret == 1)

    def putnr(self, key, value):
        """
        Store a record into a remote database object without response from the server.
        If successful, the return value is true, else, it is false.
        If a record with the same key exists in the database, it is overwritten.
        """
        cdef Py_ssize_t key_len, value_len
        cdef char *c_key, *c_value
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)
        ret = tcrdb.tcrdbput(self.tcrdb, c_key, key_len, c_value, value_len)

        return (ret == 1)

    def get(self, key):
        """
        Retrieve a record in a remote database object.
        If successful, the return value is the pointer to the region of the value of the corresponding
        record.  None is returned if no record corresponds.
        """
        cdef Py_ssize_t key_len
        cdef char *c_key
        cdef void *c_value
        cdef int sp

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        c_value = tcrdb.tcrdbget(self.tcrdb, c_key, key_len, &sp)

        if c_value != NULL:
            val = PyString_FromStringAndSize(<char *>c_value, sp)
        else:
            val = None

        return val

    def get_multi(self, keys):
        """
        Retrieve records in a remote database object.
        `keys` specifies a sequence object containing the retrieval keys.  As a result of this function,
        the return value is a dict consisted of keys existing in the database and corresponding values.
        """
        cdef int nkeys
        cdef TCMAP *map
        cdef bool ret

        cdef char *c_key, *c_value
        cdef int key_len, value_len

        DEF empty = ""

        _complain_ifclosed(self.closed)

        nkeys = len(keys)

        map = tcutil.tcmapnew2(<uint32_t>nkeys)
        if map == NULL:
            raise MemoryError()

        for i from 0 <= i < nkeys:
            c_key = PyString_AsString(keys[i])
            if c_key == NULL:
                tcutil.tcmapdel(map)
                # FIX ME!
                raise ValueError(keys[i])

            tcutil.tcmapput2(map, c_key, empty)

        ret = tcrdb.tcrdbget3(self.tcrdb, map)
        if ret != 1:
            tcutil.tcmapdel(map)
            return None

        result = {}
        try:
            tcutil.tcmapiterinit(map)
            c_key = <char *>tcutil.tcmapiternext(map, &key_len)
            while c_key != NULL:
                c_value = <char *>tcutil.tcmapget(map, c_key, key_len, &value_len)
                if c_value == NULL:
                    # TODO
                    tcutil.free(c_key)
                else:
                    key = PyString_FromStringAndSize(c_key, key_len)
                    val = PyString_FromStringAndSize(c_value, value_len)
                    result[key] = val
                c_key = <char *>tcutil.tcmapiternext(map, &key_len)
        finally:
            tcutil.tcmapdel(map)

        return result

    def out(self, key):
        """
        Remove a record of a remote database object.
        If successful, the return value is true, else, it is false.
        If a record with the same key exists in the database, it is overwritten.
        """
        cdef Py_ssize_t key_len
        cdef char *c_key
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        ret = tcrdb.tcrdbout(self.tcrdb, c_key, key_len)

        return (ret == 1)

    def vsiz(self, key):
        """
        Get the size of the value of a record in a remote database object.
        """
        cdef Py_ssize_t key_len
        cdef char *c_key
        cdef int ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        ret = tcrdb.tcrdbvsiz(self.tcrdb, c_key, key_len)

        return ret

    def iterinit(self):
        """
        Initialize the iterator of a remote database object.
        If successful, the return value is true, else, it is false.
        The iterator is used in order to access the key of every record stored in a database.
        """
        cdef bool ret
        _complain_ifclosed(self.closed)
        ret = tcrdb.tcrdbiterinit(self.tcrdb)
        return (ret == 1)

    def iternext(self):
        """
        Get the next key of the iterator of a remote database object.
        If successful, the return value is a str of the next key, else it is None.
        """
        cdef int sp
        cdef void *c_value

        _complain_ifclosed(self.closed)

        c_value = tcrdb.tcrdbiternext(self.tcrdb, &sp)
        if c_value != NULL:
            val = PyString_FromStringAndSize(<char *>c_value, sp)
        else:
            val = None

        return val

    def addint(self, key, int num):
        """
        Add an integer to a record in a remote database object.
        If successful, the return value is the summation value, else, it is `INT_MIN'.
        If the corresponding record exists, the value is treated as an integer and is added to.
        If no record corresponds, a new record of the additional value is stored.
        """
        cdef Py_ssize_t key_len
        cdef char *c_key
        cdef int ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        ret = tcrdb.tcrdbaddint(self.tcrdb, c_key, key_len, num)

        return ret

    def adddouble(self, key, double num):
        """
        Add a real number to a record in a remote database object.
        If successful, the return value is the summation value, else, it is `NAN'.
        If the corresponding record exists, the value is treated as a real number and is added to.
        If no record corresponds, a new record of the additional value is stored.
        """
        cdef Py_ssize_t key_len
        cdef char *c_key
        cdef double ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(key, &c_key, &key_len)
        ret = tcrdb.tcrdbadddouble(self.tcrdb, c_key, key_len, num)

        return ret

    def ext(self, name, key, value, int opts=0):
        """
        Call a function of the scripting language extension.
        """
        cdef char *c_name, *c_key, *c_value
        cdef Py_ssize_t key_len, value_len
        cdef int sp
        cdef char *ret

        _complain_ifclosed(self.closed)

        c_name = PyString_AsString(name)
        if c_name == NULL:
            # TODO
            raise MemoryError()

        PyString_AsStringAndSize(key, &c_key, &key_len)
        PyString_AsStringAndSize(value, &c_value, &value_len)

        ret = <char *>tcrdb.tcrdbext(self.tcrdb, c_name, opts,
                                     c_key, key_len, c_value, value_len, &sp)
        if ret == NULL:
            raise ValueError(errmsg(self.ecode()))

        result = PyString_FromStringAndSize(ret, sp)
        return result

    def sync(self):
        """
        Synchronize updated contents of a remote database object with the file and the device.
        If successful, the return value is true, else, it is false.
        This function fails and has no effect for on-memory database.
        """
        cdef bool ret
        _complain_ifclosed(self.closed)
        ret = tcrdb.tcrdbsync(self.tcrdb)
        return (ret == 1)

    def vanish(self):
        """
        Remove all records of a remote database object.
        If successful, the return value is true, else, it is false.
        """
        cdef bool ret
        _complain_ifclosed(self.closed)
        ret = tcrdb.tcrdbvanish(self.tcrdb)
        return (ret == 1)

    def fwmkeys(self, prefix, int max=-1):
        """
        Get forward matching keys in a remote database object.
        """
        cdef Py_ssize_t prefix_len
        cdef char *c_prefix
        cdef TCLIST *ret
        cdef int len, idx
        cdef void *c_value
        cdef int sp

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(prefix, &c_prefix, &prefix_len)

        ret = tcrdb.tcrdbfwmkeys(self.tcrdb, c_prefix, prefix_len, max)
        if ret == NULL:
            return None

        result = []
        try:
            len = tcutil.tclistnum(ret)
            for idx from 0 <= idx < len:
                c_value = tcutil.tclistval(ret, idx, &sp)
                if c_value != NULL:
                    val = PyString_FromStringAndSize(<char *>c_value, sp)
                    result.append(val)
                else:
                    result.append(None)
        finally:
            tcutil.tclistdel(ret)

        return result

    def copy(self, path):
        """
        Copy the database file of a remote database object.
        """
        cdef Py_ssize_t path_len
        cdef char *c_path
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(path, &c_path, &path_len)
        ret = tcrdb.tcrdbcopy(self.tcrdb, path)
        return (ret == 1)

    def restore(self, path, long ts):
        """
        Restore the database file of a remote database object from the update log.
        """
        cdef Py_ssize_t path_len
        cdef char *c_path
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(path, &c_path, &path_len)
        ret = tcrdb.tcrdbrestore(self.tcrdb, path, <uint64_t>ts)
        return (ret == 1)

    def setmst(self, host, int port):
        """
        Set the replication master of a remote database object from the update log.
        """
        cdef Py_ssize_t host_len
        cdef char *c_host
        cdef bool ret

        _complain_ifclosed(self.closed)

        PyString_AsStringAndSize(host, &c_host, &host_len)
        ret = tcrdb.tcrdbsetmst(self.tcrdb, host, port)
        return (ret == 1)

    def rnum(self):
        """
        Get the number of records of a remote database object.
        """
        cdef uint64_t ret
        _complain_ifclosed(self.closed)
        ret = tcrdb.tcrdbrnum(self.tcrdb)
        return ret

    def size(self):
        """
        Get the size of the database of a remote database object.
        """
        cdef uint64_t ret
        _complain_ifclosed(self.closed)
        ret = tcrdb.tcrdbsize(self.tcrdb)
        return ret

    def stat(self):
        """
        Get the status of the database of a remote database object.
        """
        cdef char *ret

        _complain_ifclosed(self.closed)

        ret = tcrdb.tcrdbstat(self.tcrdb)
        if ret == NULL:
            raise IOError(errmsg(self.ecode()))

        val = PyString_FromString(ret)

        result = {}
        for x in val.split('\n'):
            if x:
                kv = x.split('\t', 1)
                if len(kv) == 2:
                    result[kv[0]] = kv[1]

        return result

def open(host, port):
    """
    Open Tokyo Tyrant(TT) database
    """
    if not isinstance(host, basestring):
        raise TypeError('invalid host "%r"' % host)

    try:
        port = int(port)
    except (TypeError, ValueError), e:
        raise TypeError('invalid port "%r", %s' % (port, e))

    return TT(host, port)
